package fun.cyhgraph.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import fun.cyhgraph.properties.WeChatProperties;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.math.BigDecimal;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

/**
 * 微信支付工具类
 */
@Component
public class WeChatPayUtil {
    //微信支付下单接口地址
    public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
    //申请退款接口地址
    public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";

    @Autowired
    private WeChatProperties weChatProperties;

    /**
     * 获取调用微信接口的客户端工具对象
     * 该方法用于创建与微信支付 API 通信的 HttpClient，支持自动签名和验签功能。通过加载商户私钥和微信平台证书，实现与微信支付系统的安全通信。
     */
    private CloseableHttpClient getClient() {
        //商户API私钥文件路径  。商户 API 私钥对象（用于生成请求签名）
        PrivateKey merchantPrivateKey = null;
        try {
            //1、merchantPrivateKey商户API私钥。 （对请求进行签名，证明商户身份）
            // PemUtil.loadPrivateKey()：微信支付 SDK 工具类，从 PEM 文件加载 RSA 私钥
            // weChatProperties.getPrivateKeyFilePath()：从配置获取私钥文件路径
            merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));

            //2、加载平台证书文件。（验证微信支付返回的签名，确保响应来自微信官方）
            // PemUtil.loadCertificate()：加载 X.509 格式证书
            // weChatProperties.getWeChatPayCertFilePath()：获取平台证书文件路径
            X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
            //wechatPayCertificates微信支付平台证书列表。也可以使用“定时更新平台证书功能”，而不需要关心平台证书的来龙去脉
            List<X509Certificate> wechatPayCertificates = Arrays.asList(x509Certificate);

            //3、构建 HttpClient 生成器：
            //创建一个WechatPayHttpClientBuilder，设置商户API私钥、商户号、商户证书列表、商户平台证书序列号
            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
                    .withWechatPay(wechatPayCertificates);

            // 4、通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签
            // builder.build()：生成配置好的 HttpClient 实例
            CloseableHttpClient httpClient = builder.build();
            return httpClient;

            // 证书文件不存在
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 发送post方式请求
     *
     * @param url
     * @param body
     */
    private String post(String url, String body) throws Exception {
        // 1、调用getClient()方法获取预配置的 HttpClient 实例
        // 该 HttpClient 已自动处理签名和验签（基于商户私钥和平台证书）
        CloseableHttpClient httpClient = getClient();

        // 2、创建HttpPost对象，设置请求头和请求体  HttpPost：Apache HttpClient 的 POST 请求对象
        HttpPost httpPost = new HttpPost(url);
        // 设置请求头
        // 设置请求头和体为JSON格式 以及设置商户证书序列号
        httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
        httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
        httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
        // 将 JSON 字符串封装为 HTTP 请求实体
        httpPost.setEntity(new StringEntity(body, "UTF-8"));

        // 3、execute(httpPost)：发送 POST 请求并获取响应
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            // EntityUtils.toString()将响应体转为字符串
            String bodyAsString = EntityUtils.toString(response.getEntity());
            return bodyAsString;
        } finally {
            // 4、关闭响应流
            httpClient.close();
            // 5、关闭 HttpClient 实例
            response.close();
        }
    }


    /**
     * 发送get方式请求
     *
     * @param url
     */
    private String get(String url) throws Exception {
        // 1、调用getClient()方法获取预配置的 HttpClient 实例
        // 该 HttpClient 已自动处理签名和验签（基于商户私钥和平台证书）
        CloseableHttpClient httpClient = getClient();

        // 2、创建HttpGet对象，设置请求头和请求体
        HttpGet httpGet = new HttpGet(url);
        // 设置请求头和体为JSON格式 以及设置商户证书序列号
        httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
        httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
        httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());

        // 3、execute(httpGet)：发送 GET 请求并获取响应
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            // EntityUtils.toString()将响应体转为字符串
            String bodyAsString = EntityUtils.toString(response.getEntity());
            return bodyAsString;
        } finally {
            // 4、关闭响应流
            httpClient.close();
            // 5、关闭 HttpClient 实例
            response.close();
        }
    }

    /**
     * jsapi下单
     *
     * @param orderNum    商户订单号
     * @param total       总金额
     * @param description 商品描述
     * @param openid      微信用户的openid
     */
    private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        // 1、创建JSON对象-存储body下单信息
        JSONObject jsonObject = new JSONObject();
        // 设置appid、商户号、商品描述、商户订单号、支付成功的回调地址
        jsonObject.put("appid", weChatProperties.getAppid());
        jsonObject.put("mchid", weChatProperties.getMchid());
        jsonObject.put("description", description);
        jsonObject.put("out_trade_no", orderNum);
        jsonObject.put("notify_url", weChatProperties.getNotifyUrl());

        // 创建JSON对象-存储金额
        JSONObject amount = new JSONObject();
        // 金额，单位为分     total.multiply(new BigDecimal(100))： 将元转换为分   四舍五入保留两位小数   .intValue():将BigDecimal转换为整数（舍弃小数部分）
        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("currency", "CNY");  // 币种

        // 添加金额
        jsonObject.put("amount", amount);

        // 创建JSON对象-存储支付者
        JSONObject payer = new JSONObject();
        payer.put("openid", openid);  // 微信用户的openid

        // 添加支付者
        jsonObject.put("payer", payer);

        // 2、将jsonObject中的全部数据封装到body中    .toJSONString 将JSON对象转为字符串
        String body = jsonObject.toJSONString();
        // 3、发送请求
        return post(JSAPI, body);   // 接口地址 与 body
    }


    /**
     * 小程序支付
     *
     * @param orderNum    商户订单号
     * @param total       金额，单位 元
     * @param description 商品描述
     * @param openid      微信用户的openid
     */
    public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        // 统一下单，生成预支付交易单。 调用jsapi方法向微信支付 API 发起统一下单请求，获取预支付交易会话标识。
        String bodyAsString = jsapi(orderNum, total, description, openid);

        // 解析返回的 JSON 数据并打印，提取prepay_id用于后续步骤。
        JSONObject jsonObject = JSON.parseObject(bodyAsString);  //JSON.parseObject: 将 JSON 格式的字符串解析为 Java 对象
        System.out.println(jsonObject);
        String prepayId = jsonObject.getString("prepay_id"); //.getString: 获取 JSON 中的字段值

        if (prepayId != null) {
            // 生成时间戳（秒级）和随机字符串。         // currentTimeMillis 毫秒级时间戳 / 1000 = 秒级时间戳
            String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);  //
            String nonceStr = RandomStringUtils.randomNumeric(32);  // 生成32位随机字符串（用于签名防重）
            // 创建列表存储参与签名的参数：appId、时间戳、随机字符串、预支付 ID
            ArrayList<Object> list = new ArrayList<>();
            list.add(weChatProperties.getAppid());
            list.add(timeStamp);
            list.add(nonceStr);
            list.add("prepay_id=" + prepayId);

            //二次签名，调起支付需要重新签名
            StringBuilder stringBuilder = new StringBuilder();
            for (Object o : list) {     // 将列表中的元素按顺序拼接成一个字符串，每个元素后面添加一个换行符 \n
                stringBuilder.append(o).append("\n");
            }
            // 创建签名
            String signMessage = stringBuilder.toString(); // 将参数列表转换为字符串
            byte[] message = signMessage.getBytes();  // 将字符串转换为字节数组

            // 创建签名对象
            // 使用 RSA-SHA256 算法对原始字符串进行签名
            // 创建 RSA 签名对象，指定签名算法为SHA256withRSA
            Signature signature = Signature.getInstance("SHA256withRSA");
            // 初始化签名对象，加载商户私钥
            signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
            signature.update(message);  // 向签名对象添加待签名数据
            String packageSign = Base64.getEncoder().encodeToString(signature.sign());  // 执行签名并对结果进行 Base64 编码

            //构造JSON数据给微信小程序，用于调起微信支付
            JSONObject jo = new JSONObject();
            jo.put("timeStamp", timeStamp);   // 时间戳
            jo.put("nonceStr", nonceStr);  // 随机字符串
            jo.put("package", "prepay_id=" + prepayId);  // 预支付交易会话标识
            jo.put("signType", "RSA");   // 签名类型
            jo.put("paySign", packageSign);  // 签名

            return jo;   // 成功生成支付参数时返回
        }
        return jsonObject;   // 预支付ID不存在时返回原始响应
    }


    /**
     * 申请退款
     *
     * @param outTradeNo  商户订单号
     * @param outRefundNo 商户退款单号
     * @param refund      退款金额
     * @param total       原订单金额
     */
    public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {
        // 1、创建JSON对象-存储body
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("out_trade_no", outTradeNo);   // 商户订单号
        jsonObject.put("out_refund_no", outRefundNo);  // 商户退款单号

        // 创建JSON对象-存储金额
        JSONObject amount = new JSONObject();
        // 退款金额，单位为分    refund.multiply(new BigDecimal(100))：将元转换为分   四舍五入保留两位小数   .intValue():将BigDecimal转换为整数（舍弃小数部分）
        amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        // 原订单金额，单位为分   total.multiply(new BigDecimal(100))： 将元转换为分   四舍五入保留两位小数   .intValue():将BigDecimal转换为整数（舍弃小数部分）
        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("currency", "CNY");  // 币种

        // 添加金额
        jsonObject.put("amount", amount);
        // 添加退款成功的回调地址
        jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());

        // 2、将jsonObject中的全部数据封装到body中 。  .toJSONString() 将JSON对象转为字符串
        String body = jsonObject.toJSONString();

        // 3、调用申请退款接口
        return post(REFUNDS, body);
    }
}
